package personal.hmm.google.authenticator;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import personal.hmm.google.authenticator.Base32;

public class GoogleAuthenticator {

	// taken from Google pam docs - we probably don't need to mess with these
	final static int secretSize = 10;
	final static int numOfScratchCodes = 5;
	final static int scratchCodeSize = 8;
	String secret;
	long timeMsec;
	int window_size = 3;

	public GoogleAuthenticator(String secret) {
		this.secret = secret;
	}

	public GoogleAuthenticator(String secret, long timeMsec) {
		this.secret = secret;
		this.timeMsec = timeMsec;
	}

	public boolean checkCode(String code) {
		Base32 codec = new Base32();
		byte[] decodedKey = codec.decodeBytes(secret);

		long t = (timeMsec / 1000L) / 30L;

		for (int i = -window_size; i <= window_size; ++i) {
			long hash;
			try {
				hash = verifyCode(decodedKey, t + i);
			} catch (Exception e) {
				e.printStackTrace();
				throw new RuntimeException(e.getMessage());
				// return false;
			}
			String strHash = String.valueOf(hash);
			if (strHash.length() < 6) {
				for (int j = 0; j < (6 - strHash.length()); j++) {
					strHash = "0" + strHash;
				}
			}
			if (strHash.equals(code)) {
				return true;
			}
		}
		return false;
	}

	public long getCode() {
		Base32 codec = new Base32();
		long hash = 0;
		byte[] decodedKey = codec.decodeBytes(secret);
		long t = (timeMsec / 1000L) / 30L;

		try {
			hash = verifyCode(decodedKey, t);
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e.getMessage());
		}
		return hash;
	}

	public static int verifyCode(byte[] key, long t)
			throws NoSuchAlgorithmException, InvalidKeyException {
		byte[] data = new byte[8];
		long value = t;
		for (int i = 8; i-- > 0; value >>>= 8) {
			data[i] = (byte) value;
		}

		SecretKeySpec signKey = new SecretKeySpec(key, "HmacSHA1");
		Mac mac = Mac.getInstance("HmacSHA1");
		mac.init(signKey);
		byte[] hash = mac.doFinal(data);

		int offset = hash[20 - 1] & 0xF;

		long truncatedHash = 0;
		for (int i = 0; i < 4; ++i) {
			truncatedHash <<= 8;
			truncatedHash |= (hash[offset + i] & 0xFF);
		}

		truncatedHash &= 0x7FFFFFFF;
		truncatedHash %= 1000000;

		return (int) truncatedHash;
	}

}
